#include <lversion.h>
#if L_VERSION_MODULE_APP_FRAMEWORK == 111225

#include "_lappFramework_111225.h"

#include <boost/make_shared.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>

#include <iostream>
#include <sstream>

using namespace std;
using namespace llib;
using namespace llib::util;
using namespace boost::posix_time;
using boost::gregorian::date;

util::_framework_prototype::_framework_prototype():
    _windowWidth    ( 600 ),
    _windowHeight   ( 400 ),
    _windowPosX     ( 100 ),
    _windowPosY     ( 100 ),
    _displayMode    ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ),
    _windowTitle    ( "Untitled" ),
    _fullScreen     ( FALSE ),
    _statusChanged  ( FALSE ),
    _mouseButton    ( MouseButtonNone ),
    _curTime        ( 0 ),
    _lastTime       ( 0 ),
    _timeInterval   ( 1000 ),
    _frameCount     ( 0 ) {}

util::_framework_prototype::~_framework_prototype()
{
    if(AppFramework.pSimulator.use_count() != 0)
        AppFramework.pSimulator->Finalize();
}

Int32 util::_framework_prototype::FrameworkMain(Int32 argc, Char** argv)
{
    if( pSimulator.use_count() == 0 ) {
        cout << "Error: AppFramework has NO simulator to perform rendering!" << endl
             << "You MUST:" << endl
             << "  1. Specify subclass instance of \"llib::Simulator\" using \"SetSimulator\";" << endl
             << "  2. Override basic methods (Initialize,Render,Finalize...);" << endl
             << "  3. Override optional methods (event callbacks)." << endl
             << endl << "Exit." << endl << endl;
        return 0;
    }

    if( !AppFramework.pSimulator->Initialize() ) {
        cout << "Error: Simulator initialization failed!" << endl
             << endl << "Exit." << endl << endl;
        return 0;
    }

    if( !AppFramework.pSimulator->Preprocess() ) {
        cout << "Error: Simulator preprocessing failed!" << endl
             << endl << "Exit." << endl << endl;
        return 0;
    }

    glutInit(&argc, argv);
    _curTime = _lastTime = glutGet(GLUT_ELAPSED_TIME);

    glutInitWindowSize( _windowWidth, _windowHeight );
    glutInitWindowPosition( _windowPosX, _windowPosY );
    glutInitDisplayMode( _displayMode );

    glutCreateWindow( _windowTitle.c_str() );
    glutIgnoreKeyRepeat(FALSE);

    glutDisplayFunc         ( displayFuncWrapper );
    glutIdleFunc            ( idleFuncWrapper );

    glutKeyboardFunc        ( keyboardFuncWrapper );
    glutKeyboardUpFunc      ( keyboardUpFuncWrapper );
    glutSpecialFunc         ( specialFuncWrapper );
    glutSpecialUpFunc       ( specialUpFuncWrapper );
    glutReshapeFunc         ( reshapeFuncWrapper );
    glutMotionFunc          ( motionFuncWrapper );
    glutMouseFunc           ( mouseFuncWrapper );
    glutPassiveMotionFunc   ( passiveMotionFuncWrapper );
    glutCloseFunc           ( closeFuncWrapper );

    statusUpdate();

    glInit();
    glutMainLoop();

    return 0;
}

void util::_framework_prototype::statusUpdate()
{
    // change full screen setting
    if( _fullScreen ) {
        glutFullScreen();
    } else {
        glutPositionWindow( _windowPosX, _windowPosY );
        glutReshapeWindow( _windowWidth, _windowHeight );
    }
    _statusChanged = FALSE;
}

void util::_framework_prototype::glInit( void )
{
    glShadeModel(GL_SMOOTH);

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    /* light config */
    GLfloat mat_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    GLfloat mat_shininess[] = { 50.0f };
    GLfloat light_position[] = { 40.0f, 40.0f, 40.0f, 0.0f };
    GLfloat white_light[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    GLfloat lmodel_ambient[] = { 0.4f, 0.4f, 0.4f, 1.0f };
    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
    glLightfv(GL_LIGHT0, GL_SPECULAR, white_light);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
}

void util::_framework_prototype::displayFuncWrapper()
{   // this display function seems to be never called ?! (2011-5-20)
    // display function called when refresh (2011-11-29)
    AppFramework.frameRender();
}

void util::_framework_prototype::idleFuncWrapper()
{
    if(AppFramework._statusChanged) AppFramework.statusUpdate();

    AppFramework.frameRender();
}

void util::_framework_prototype::keyboardFuncWrapper(Uint8 key, Int32 x, Int32 y)
{
    Uint8 m = Uint8( glutGetModifiers() );
    AppFramework.pSimulator->OnKeyDown( key, x, y, m );
}

void util::_framework_prototype::keyboardUpFuncWrapper(Uint8 key, Int32 x, Int32 y)
{
    // exit application when ESC is pressed
    if( key == 27 ) {
        glutLeaveMainLoop();
        return;
    }

    Uint8 m = Uint8( glutGetModifiers() );
    AppFramework.pSimulator->OnKeyUp( key, x, y, m );
}

void util::_framework_prototype::specialFuncWrapper(Int32 key, Int32 x, Int32 y)
{
    Uint8 m = Uint8( glutGetModifiers() );
    AppFramework.pSimulator->OnSpecialKeyDown( Uint8(key), x, y, m );
}

void util::_framework_prototype::specialUpFuncWrapper(Int32 key, Int32 x, Int32 y)
{
    // toggle full screen flag when F11 is pressed
    if( key == GLUT_KEY_F11 ) {
        AppFramework._fullScreen = !AppFramework._fullScreen;
        AppFramework._statusChanged = TRUE;
    }

    Uint8 m = Uint8( glutGetModifiers() );
    AppFramework.pSimulator->OnSpecialKeyUp( Uint8(key), x, y, m );
}

void llib::util::_framework_prototype::reshapeFuncWrapper( Int32 width, Int32 height )
{
    if(height == 0) height = 1;

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void llib::util::_framework_prototype::mouseFuncWrapper(Int32 button, Int32 state, Int32 x, Int32 y)
{
    Uint8 mouseButton = AppFramework._mouseButton;
    switch (button) {
        /* left, middle, right buttons */
        case GLUT_LEFT_BUTTON:
            if( state == GLUT_DOWN ) {
                AppFramework.pSimulator->OnMouseButtonDown(MouseButtonLeft, x, y);
                mouseButton = MouseButtonLeft | mouseButton;
            } else { /* statc == GLUT_UP */
                AppFramework.pSimulator->OnMouseButtonUp(MouseButtonLeft, x, y);
                mouseButton = ~MouseButtonLeft & mouseButton;
            }
            break;
        case GLUT_MIDDLE_BUTTON:
            if( state == GLUT_DOWN ) {
                AppFramework.pSimulator->OnMouseButtonDown(MouseButtonMiddle, x, y);
                mouseButton = MouseButtonMiddle | mouseButton;
            } else { /* statc == GLUT_UP */
                AppFramework.pSimulator->OnMouseButtonUp(MouseButtonMiddle, x, y);
                mouseButton = ~MouseButtonMiddle & mouseButton;
            }
            break;
        case GLUT_RIGHT_BUTTON:
            if( state == GLUT_DOWN ) {
                AppFramework.pSimulator->OnMouseButtonDown(MouseButtonRight, x, y);
                mouseButton = MouseButtonRight | mouseButton;
            } else { /* statc == GLUT_UP */
                AppFramework.pSimulator->OnMouseButtonUp(MouseButtonRight, x, y);
                mouseButton = ~MouseButtonRight & mouseButton;
            }
            break;

        /* wheel */
        case 3:
            if( state == GLUT_DOWN ) {
                AppFramework.pSimulator->OnMouseWheel(MouseWheelBackward, x, y);
            }
            break;
        case 4:
            if( state == GLUT_DOWN ) {
                AppFramework.pSimulator->OnMouseWheel(MouseWheelForward, x, y);
            }
            break;

        /* others */
        default:
            // do nothing
            break;
    }
    AppFramework._mouseButton = mouseButton;
}

void llib::util::_framework_prototype::motionFuncWrapper( Int32 x, Int32 y )
{
    AppFramework.pSimulator->OnMouseMove(AppFramework._mouseButton, x, y);
}

void llib::util::_framework_prototype::passiveMotionFuncWrapper( Int32 x, Int32 y )
{
    AppFramework.pSimulator->OnMouseMove(AppFramework._mouseButton, x, y);
}

void llib::util::_framework_prototype::closeFuncWrapper( void )
{
    AppFramework.pSimulator->Finalize();
}

void llib::util::_framework_prototype::frameRender( void )
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    pSimulator->Render();

    glFlush();
    glutSwapBuffers();

    /* window title information update */
    static ostringstream ss;
    ++_frameCount;
    _curTime = glutGet(GLUT_ELAPSED_TIME);
    if(_curTime - _lastTime > _timeInterval) {
        /* FPS */
        Float fps = Float(_frameCount * 1000.0 / (_curTime - _lastTime));
        /* Date time */
        ptime now = second_clock::local_time();
        date& now_date = now.date();
        time_duration& now_time = now.time_of_day();
        /* Construct new title */
        ss << _windowTitle << " | ";
        Int32 year      = now_date.year();
        Int32 month     = now_date.month();
        Int32 day       = now_date.day();
        Int32 hours     = now_time.hours();
        Int32 minutes   = now_time.minutes();
        ss << year << '.' << month << '.' << day << ' ' << hours << ':' << minutes << " | ";
        Int32 i_part = Int32(fps);
        Int32 f_part = Int32((fps-i_part)*100);
        ss << "FPS = " << i_part << '.' << f_part;
        glutSetWindowTitle(ss.str().c_str());
        ss.str("");
        _lastTime = _curTime;
        _frameCount = 0;
    }
}

#endif
